docs(spec): app.homePageId 墓碑说清真正的退役理由 —— 「no shell ever read it」是假的 (#4709) - #4846
Conversation
#4709) 改的是「为什么删」的表述,不是删本身。`homePageId` 依旧退役(`retiredKey`:编译期 `never`、解析期报错),conversion `app-dead-authoring-keys-removed` 的行为、四张 baseline、`os migrate meta --from 16` 的处方一字未动。 #4667 的理由「no shell ever read it」是假的,且与本仓自己的记录矛盾:2026-06 的 AppSchema liveness 审计把 `homePageId` 列在 LIVE 一侧,因为 objectui console 的 `resolveLandingRoute()`(`packages/app-shell/src/console/AppContent.tsx`, objectui @785b8a5d)一直在读它,而且它是唯一决定 app 落地页的地方。两份文档矛盾 两个月无人发现 —— 假前提会让下一个读者据此做错判断,#4709 立单人正是先信了这句。 真正让它该走的是形状:把落地页编码成指向 `navigation` 的 ID 交叉引用,没有引用 完整性,id 悬空时静默回退到第一项。将来若要「落地页 ≠ 第一项」,正确形状是导航项 自身的标记(`navigation[].landing`),按 enforce-first 设计。 逐处纠正:`ui/app.zod.ts` 墓碑与 docblock、conversion 摘要(经 gen 重生成到 `docs/protocol-upgrade-guide.md` 与 `spec-changes.json`)、生成文档 `content/docs/references/ui/app.mdx`、`content/docs/ui/apps.mdx`、liveness ledger 的 `homePageId` note、`examples/app-showcase` 的过期注释、#4667 未消费 changeset 的论证段;6 月审计加后续注记(原结论不动)。新增 pin 测试防假前提回潮。 objectui 侧的 `if (homePageId)` 死分支另见 objectstack-ai/objectui#3264。 Fixes #4709 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
…epageid-tombstone-copy
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…epageid-tombstone-copy
`packages/spec/spec-changes.json` 与 `docs/protocol-upgrade-guide.md` 在 `.gitattributes` 里走 `merge=os-regen`。合并 origin/main(含 #4831 退役 activationEvents)后实测:两文件**零冲突标记**地只保留了本分支一侧 —— `plugin-activation-events-retired` 被静默吞掉(`grep -c` = 0),正是 #4616 预警的失效形态。 按规矩不做文本合并:`git checkout origin/main --` 取回两文件后,从合并后的 **源**(`conversions/registry.ts` 的 summary + `migrations/registry.ts` 的 step17.semantic)整体重新生成,得到正确并集。 重生成后核对:两条条目并存;#4616 的四个删除(system/EmailTemplate、 SMSTemplate、PushNotification、InAppNotification)未复活,幸存的 EmailTemplateDefinition 仍在;#4657/#4831 的 kernel/studio ActivationEvent def 与 ActivationEventSchema 导出未复活,authorable-surface 里 activationEvents 仅剩 #4831 自己的 `[RETIRED]` 墓碑行。五张 ratchet 重生成后与 main 逐字节一致, 本分支一张未动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
与 #4831 同步完成 ——
|
| 文件 | 本分支一侧(unread or wrongly encoded) |
main 一侧 #4831(plugin-activation-events-retired) |
|---|---|---|
packages/spec/spec-changes.json |
grep -c = 2 ✅ |
grep -c = 0 ❌ 被吞 |
docs/protocol-upgrade-guide.md |
grep -c = 1 ✅ |
grep -c = 0 ❌ 被吞 |
即:若直接采信这次「干净合并」,#4831 刚落地的 D3 条目会无声消失在我这条 PR 里。#4616 的教训在本分支上一次复现成功。
两侧的源都完好(合并本身没问题,坏的是生成物的文本合并):conversions/registry.ts 我的更正 = 1,migrations/registry.ts 的 #4831 条目 = 1。
3. 修复路线:不做文本合并,整体重生成
git checkout origin/main -- packages/spec/spec-changes.json docs/protocol-upgrade-guide.md
pnpm --filter @objectstack/spec build && gen:schema && gen:spec-changes && gen:upgrade-guide && gen:docs
(我的更正本就派生自 conversions/registry.ts 的 summary,#4831 的条目派生自 migrations/registry.ts,故从合并后的源重生成必然产出正确并集。)
pre-commit 的 os-regen 钩子独立复核并确认了这一路径:
os-regen: 2 generated artifact(s) were merged WITHOUT a text merge and must be regenerated
from the merged tree before this commit.
✓ packages/spec/spec-changes.json — current
✓ docs/protocol-upgrade-guide.md — current
os-regen: all deferred artifacts are current — marker cleared.
4. 核对断言(第 5 条)
并存 —— 重生成后:
spec-changes.json : app-dead-authoring-keys-removed(我的 "unread or wrongly encoded") = 2 ✅
spec-changes.json : plugin-activation-events-retired (#4831) = 2 ✅
protocol-upgrade-guide.md : 我的更正 = 1 ✅ ; activationEvents (#4831) = 4 ✅
未复活 #4616 的删除(精确名,json-schema.manifest.json):
system/EmailTemplate = 0 ✅ system/SMSTemplate = 0 ✅
system/PushNotification = 0 ✅ system/InAppNotification = 0 ✅
system/EmailTemplateDefinition = 3 ✅(幸存邻居,不同 def,应当在)
kernel/ActivationEvent (manifest) = 0 ✅
studio/ActivationEvent (manifest) = 0 ✅
ActivationEventSchema (api-surface.json) = 0 ✅
authorable-surface.json 里 activationEvents 唯一命中:
4284: "kernel/DynamicLoadRequest:activationEvents [RETIRED]" ← #4831 自己的墓碑行 ✅
五张 ratchet 仍零触碰:重生成后 git status 只有那两个文件,api-surface.json / api-surface-signatures.json / authorable-surface.json / json-schema.manifest.json / dual-source-exports.baseline.json 与合并后的 main 逐字节一致,本分支一张未动。
5. 同步后重跑读数
### check:generated
✓ check:spec-changes spec-changes.json
✓ check:upgrade-guide docs/protocol-upgrade-guide.md
✓ check:skill-docs skill docs (from SKILL.md frontmatter)
✓ check:skill-refs skill references
✓ check:react-blocks react-blocks contract
✓ check:authorable-surface authorable-surface.json + JSON schemas
✓ check:api-surface api-surface.json
✓ check:docs content/docs/references/**
✓ All 8 generated artifacts are up to date.
rc-generated=0
### check:dual-source-exports
✅ no new dual-source exports: 4276 names across 16 entry points — 171 re-exported
(single declaration), 0 accepted dual-source (baseline).
rc-dual=0 ← 0,符合要求
### check:liveness
✓ all governed-type properties are classified, every registered type is governed or explicitly
pending, no ledger row outlives its property, and all bound high-risk proofs resolve.
rc-liveness=0
### spec test(全量)
Test Files 295 passed (295)
Tests 7359 passed (7359) ← 与 #4831 自报的 295/7358 一致(+1 = 本 PR 新增的 pin)
### 全仓 typecheck
Tasks: 122 successful, 122 total
Time: 7m52.436s
rc-full-tc=0
6. 其他
- 全程
git merge,未 rebase、未 force-push。 - push 后 main 又前进 4 个 commit(
0657f6bdc/0b795da8e/fa94b2cd3/f4847a6b5),经核对均未触碰任何共同冲突面(spec-changes.json/protocol-upgrade-guide.md/ 五张 ratchet / 两个 registry),故无需再次同步。 - PR 仍为 draft,未 ready、未 auto-merge。
Generated by Claude Code
os-regen 第三轮实测:#4846 对 app-dead-authoring-keys-removed summary 的散文修正 ('WAS read by objectui')在合并中被静默吞掉(origin/main 2 处命中,合并后 0)。 按协议 checkout origin/main 后从合并源整体重生成;spec-changes.json 现三方并存: #4657 activationEvents 4 条、#4846 散文 2 处、#4793 batch-row 2 条。 四张 ratchet 与 origin/main 逐字节相同(本单导出增删 0)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
Fixes #4709
按 #4709 维护者裁决 B(维持退役) 的强制附带项 (1) 墓碑与文档文案纠错。
一、objectui 消费点实测读数
objectstack-ai/objectui@785b8a5(本会话挂载于/workspace/objectui),packages/app-shell/src/console/AppContent.tsx:函数体(实读):
结论:console 读过这个键,而且它是唯一决定 app 内落地页的地方。所以 #4667 墓碑里的「no shell ever read it」为假。
顺带,这段实现本身就是裁决 B 的论据:
findNavItemById找不到时route为空,静默落回findFirstRoute—— 悬空 ID 无声回退,正是不该用 ID 交叉引用表达落地页的原因。二、6 月审计的矛盾证据
docs/audits/2026-06-appschema-property-liveness.md:18,标题为 「LIVE & necessary (the sidebar core)」 的清单里明确含homePageId:即 6 月审计说 live、8 月退役理由说 never read,两份本仓文档直接矛盾,两个月无人发现 —— 发现者是做 cloud pin bump 对账的第三方会话(#4709)。本 PR 不改这份审计的结论(它是对的),只在其后加一条指向 #4667/#4709 的后续注记,并把「退役单引用 liveness 必须对账既有审计」这条流程教训写进去。
三、
.changeset/book-job-translation-app-authorwarn-keys-retired.md是否已消费 —— 判定:未消费,故就地纠正三条独立证据:
.changeset/(未被changeset version删除);.changeset/pre.json(mode: pre,tag: rc)的changesets数组共 641 条,不含book-job-translation-app-authorwarn-keys-retired;packages/spec/CHANGELOG.md里grep homePageId零命中(当前顶部为17.0.0-rc.1)。三者一致 ⇒ 该 changeset 尚未进入任何已发布 CHANGELOG,不构成历史记录,按任务书就地纠正其论证段(把「its own hedge」换成真实理由),不另立「纠正说明」。
四、逐处改动清单
packages/spec/src/ui/app.zod.tsHOME_PAGE_ID_RETIRED墓碑文案改为诚实版本(承认 objectui console 曾消费,给出真实理由:ID 交叉引用 + 悬空静默回退);上方 docblock 同步重写,记录假前提、6 月审计矛盾、以及「将来正确形状是navigation[].landing,enforce-first」。处方部分逐字保留content/docs/references/ui/app.mdx:86retiredKey()的.describe()重生成gen:docspackages/spec/src/conversions/registry.ts:1623app-dead-authoring-keys-removed的summary散文:never read→unread or wrongly encoded,并把 homePageId 分句换成真实理由。其他键的描述一字未动;id/surface/apply/fixture/expectedNotices全部未动docs/protocol-upgrade-guide.md:200GENERATED (ADR-0087 D4) — do not edit by hand,只能经 3 重生成gen:upgrade-guidepackages/spec/spec-changes.json(2 处)gen:spec-changes.changeset/book-job-translation-app-authorwarn-keys-retired.md:42packages/lint/src/lint-liveness-properties.test.ts:268/341app.homePageId等)说明 lint 已对其静默,没有复述任何假前提docs/audits/2026-06-appschema-property-liveness.mdLIVE清单后新增引用块注记:该键已于 v17 退役、本审计的 LIVE 判定是对的、退役理由已在 #4709 更正,并记录流程教训packages/spec/liveness/app.json(homePageId.note)No shell ever read it。status保持dead、verifiedAt未动,只改 note 散文。理由:ledger 正是下一个退役单会去读的记录,假前提留在这里就是复发源content/docs/ui/apps.mdx:288The key was removed in 17.0.0 (#4667) because nothing ever read it.—— 同一句假话,改为真实理由examples/app-showcase/src/ui/apps/index.ts:29the spec's homePageId has no console consumer yet—— 既假(console 消费过)又过期(键已退役),改为「homePageId已于 17.0.0 移除,首项即落地页」packages/spec/src/ui/app.test.ts.changeset/homepageid-tombstone-premise-corrected.md@objectstack/spec: patch核实后判定不改的:
packages/spec/liveness/README.md:511的homePageId分句为「the landing IS the first nav item; root landing followsisDefaultrouting」—— 描述的是 v17 之后的现状,事实成立,不含never read类假前提;为控制 blast radius(该行是极易冲突的巨型汇总行)不动。content/docs/ui/apps.mdx:181同理(陈述现状,成立)。五、新增回归 pin
packages/spec/src/ui/app.test.ts新增the tombstone states the REAL reason, not the false "no shell read it" premise (#4709):retiredKey(guidance)把同一份guidance同时用作解析期报错和.describe(),所以这条 pin 同时锁住了报错文案与生成文档文案。六、changeset 定级
@objectstack/spec: patch。定级从实:本 PR 不改任何运行时行为、不改类型、不改AppSchema接受/拒绝的输入集合 —— 只改一条已经在拒绝的键所附带的解释性文字。同一批未发布的 major(#4667)已在队列中,patch 与之叠加无害。正文明确写了「改的是退役理由的表述,不是退役本身」。七、验证(实测输出)
合并
origin/main后复验(merge 而非 rebase;四张 ratchet 无重叠 —— main 的 #4809 动了api-surface.json/authorable-surface.json/json-schema.manifest.json,而本 PR 一张都没动,故无需git checkout origin/main --重生成):八、明确不做
retiredKey()、authorable-surface.json、app-dead-authoring-keys-removed的行为条目。content/docs/releases/一个字未碰。if (homePageId)死分支与findNavItemById的清理、以及 docblock 里「CRM example 落在 Sales Dashboard」那句承诺的更正,均属跨仓项 (2),另见objectstack-ai/objectui#3264,不在本 PR 内。navigation[].landing,enforce-first)已写进app.zod.tsdocblock 与 liveness ledger note,防 v18 回潮。🤖 Generated with Claude Code
https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
Generated by Claude Code